/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlet.container.services;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.portlet.PortletRequest;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.container.PortletContainerException;
import org.apache.pluto.container.PortletWindow;
import org.apache.pluto.container.UserInfoService;
import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
import org.apache.pluto.container.om.portlet.UserAttribute;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.portlet.registry.IPortletEntityRegistry;
import org.apereo.portal.portlet.registry.IPortletWindowRegistry;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.ISecurityContext;
import org.apereo.portal.security.provider.cas.CasProxyTicketAcquisitionException;
import org.apereo.portal.security.provider.cas.ICasSecurityContext;
import org.apereo.portal.url.IPortalRequestUtils;
import org.apereo.portal.user.IUserInstance;
import org.apereo.portal.user.IUserInstanceManager;
import org.springframework.beans.factory.annotation.Autowired;

/** Requests a CAS Proxy ticket for the current portlet and adds it to the Pluto UserInfoService. */
public class CasTicketUserInfoService implements UserInfoService {

    private IUserInstanceManager userInstanceManager;
    private IPortletWindowRegistry portletWindowRegistry;
    private IPortletEntityRegistry portletEntityRegistry;
    private IPortletDefinitionRegistry portletDefinitionRegistry;
    private IPortalRequestUtils portalRequestUtils;
    protected final Log log = LogFactory.getLog(getClass());

    /** @param portalRequestUtils the portalRequestUtils to set */
    @Autowired
    public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
        this.portalRequestUtils = portalRequestUtils;
    }

    /** The default name of the preferences attribute used to pass the PT to the portlet. */
    private String proxyTicketKey = "casProxyTicket";

    /** @return the UserInstanceManager */
    public IUserInstanceManager getUserInstanceManager() {
        return userInstanceManager;
    }
    /** @param userInstanceManager the UserInstanceManager */
    @Autowired
    public void setUserInstanceManager(IUserInstanceManager userInstanceManager) {
        this.userInstanceManager = userInstanceManager;
    }

    /** @return the portletEntityRegistry */
    public IPortletEntityRegistry getPortletEntityRegistry() {
        return this.portletEntityRegistry;
    }
    /** @param portletEntityRegistry the portletEntityRegistry to set */
    @Autowired
    public void setPortletEntityRegistry(IPortletEntityRegistry portletEntityRegistry) {
        this.portletEntityRegistry = portletEntityRegistry;
    }

    /** @return the portletWindowRegistry */
    public IPortletWindowRegistry getPortletWindowRegistry() {
        return this.portletWindowRegistry;
    }
    /** @param portletWindowRegistry the portletWindowRegistry to set */
    @Autowired
    public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
        this.portletWindowRegistry = portletWindowRegistry;
    }

    /** @return the portletDefinitionRegistry */
    public IPortletDefinitionRegistry getPortletDefinitionRegistry() {
        return this.portletDefinitionRegistry;
    }
    /** @param portletDefinitionRegistry the portletDefinitionRegistry to set */
    @Autowired
    public void setPortletDefinitionRegistry(IPortletDefinitionRegistry portletDefinitionRegistry) {
        this.portletDefinitionRegistry = portletDefinitionRegistry;
    }

    /** @return name of the key to save the proxy ticket under */
    public String getProxyTicketKey() {
        return proxyTicketKey;
    }
    /** @param proxyTicketKey name of the key to save the proxy ticket under */
    public void setProxyTicketKey(String proxyTicketKey) {
        this.proxyTicketKey = proxyTicketKey;
    }

    /*
     * (non-Javadoc)
     * @see org.apache.pluto.container.UserInfoService#getUserInfo(javax.portlet.PortletRequest, org.apache.pluto.container.PortletWindow)
     */
    @Override
    public Map<String, String> getUserInfo(PortletRequest request, PortletWindow portletWindow)
            throws PortletContainerException {

        Map<String, String> userInfo = new HashMap<String, String>();

        // check to see if a CAS proxy ticket is expected by this portlet
        if (isCasProxyTicketRequested(request, portletWindow)) {

            // if it is, attempt to request a proxy ticket
            String proxyTicket = getProxyTicket(request);
            if (proxyTicket != null) userInfo.put(this.proxyTicketKey, proxyTicket);
        }
        return userInfo;
    }

    /**
     * Determine whether the portlet has expects a CAS proxy ticket as one of the user attributes.
     *
     * @param request portlet request
     * @param plutoPortletWindow portlet window
     * @return <code>true</code> if a CAS proxy ticket is expected, <code>false</code> otherwise
     * @throws PortletContainerException if expeced attributes cannot be determined
     */
    public boolean isCasProxyTicketRequested(
            PortletRequest request, PortletWindow plutoPortletWindow)
            throws PortletContainerException {

        // get the list of requested user attributes
        final HttpServletRequest httpServletRequest =
                this.portalRequestUtils.getPortletHttpRequest(request);
        final IPortletWindow portletWindow =
                this.portletWindowRegistry.convertPortletWindow(
                        httpServletRequest, plutoPortletWindow);
        final IPortletEntity portletEntity = portletWindow.getPortletEntity();
        final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
        final PortletApplicationDefinition portletApplicationDescriptor =
                this.portletDefinitionRegistry.getParentPortletApplicationDescriptor(
                        portletDefinition.getPortletDefinitionId());

        // check to see if the proxy ticket key is one of the requested user attributes
        List<? extends UserAttribute> requestedUserAttributes =
                portletApplicationDescriptor.getUserAttributes();
        for (final UserAttribute userAttributeDD : requestedUserAttributes) {
            final String attributeName = userAttributeDD.getName();
            if (attributeName.equals(this.proxyTicketKey)) return true;
        }

        // if the proxy ticket key wasn't found in the list of requested attributes
        return false;
    }

    /**
     * Attempt to get a proxy ticket for the current portlet.
     *
     * @param request portlet request
     * @return a proxy ticket, or <code>null</code> if we were unsuccessful
     */
    private String getProxyTicket(PortletRequest request) {

        final HttpServletRequest httpServletRequest =
                this.portalRequestUtils.getPortletHttpRequest(request);

        // try to determine the URL for our portlet
        String targetService = null;
        try {
            URL url = null;

            // if the server port is 80 or 443, don't include it in the URL
            int port = request.getServerPort();
            if (port == 80 || port == 443)
                url =
                        new URL(
                                request.getScheme(),
                                request.getServerName(),
                                request.getContextPath());
            else
                url =
                        new URL(
                                request.getScheme(),
                                request.getServerName(),
                                request.getServerPort(),
                                request.getContextPath());
            targetService = url.toString();

        } catch (MalformedURLException e) {
            log.error("Failed to create a URL for the target portlet", e);
            e.printStackTrace();
            return null;
        }

        // get the CasSecurityContext
        final IUserInstance userInstance = userInstanceManager.getUserInstance(httpServletRequest);
        final IPerson person = userInstance.getPerson();
        final ISecurityContext context = person.getSecurityContext();
        if (context == null) {
            log.error("no security context, no proxy ticket passed to the portlet");
            return null;
        }
        ISecurityContext casContext = getCasContext(context);
        if (casContext == null) {
            log.debug("no CAS security context, no proxy ticket passed to the portlet");
            return null;
        }
        if (!casContext.isAuthenticated()) {
            log.debug("no CAS authentication, no proxy ticket passed to the portlet");
            return null;
        }

        // get a proxy ticket for our portlet from the CasSecurityContext
        String proxyTicket = null;
        try {
            proxyTicket = ((ICasSecurityContext) casContext).getCasServiceToken(targetService);
            log.debug("Put proxy ticket in userinfo: " + proxyTicket);
        } catch (CasProxyTicketAcquisitionException e) {
            log.error("no proxy ticket passed to the portlet: " + e);
        }

        return proxyTicket;
    }

    /**
     * Looks for a security context
     *
     * @param context the principal security context
     * @return the CAS security context, or null if not found.
     */
    @SuppressWarnings("unchecked")
    private static ISecurityContext getCasContext(ISecurityContext context) {
        if (context instanceof ICasSecurityContext) {
            return context;
        }
        Enumeration contextEnum = context.getSubContexts();
        while (contextEnum.hasMoreElements()) {
            ISecurityContext subContext = (ISecurityContext) contextEnum.nextElement();
            if (subContext instanceof ICasSecurityContext) {
                return subContext;
            }
        }
        return null;
    }
}
